use serde::de::Visitor;
use serde::de::{DeserializeSeed, EnumAccess, MapAccess, SeqAccess, Unexpected, VariantAccess};

#[cfg(feature = "napi6")]
use crate::bindgen_runtime::BigInt;
use crate::{
  bindgen_runtime::{ArrayBuffer, BufferSlice, FromNapiValue, JsObjectValue, Object, Unknown},
  type_of, Error, JsValue, Result, Status, Value, ValueType,
};

pub struct De<'env>(pub(crate) &'env Value);
impl<'env> De<'env> {
  pub fn new(value: &'env Object<'env>) -> Self {
    Self(&value.0)
  }
}

#[doc(hidden)]
impl<'x> serde::de::Deserializer<'x> for &mut De<'_> {
  type Error = Error;

  fn deserialize_any<V>(self, visitor: V) -> Result<V::Value>
  where
    V: Visitor<'x>,
  {
    let js_value_type = type_of!(self.0.env, self.0.value)?;
    match js_value_type {
      ValueType::Null | ValueType::Undefined => visitor.visit_unit(),
      ValueType::Boolean => {
        let val: bool = unsafe { FromNapiValue::from_napi_value(self.0.env, self.0.value)? };
        visitor.visit_bool(val)
      }
      ValueType::Number => {
        let js_number: f64 = unsafe { FromNapiValue::from_napi_value(self.0.env, self.0.value)? };
        if (js_number.trunc() - js_number).abs() < f64::EPSILON {
          visitor.visit_i64(js_number as i64)
        } else {
          visitor.visit_f64(js_number)
        }
      }
      ValueType::String => visitor.visit_str(
        unsafe { <String as FromNapiValue>::from_napi_value(self.0.env, self.0.value) }?.as_str(),
      ),
      ValueType::Object => {
        let js_object = Object::from_raw(self.0.env, self.0.value);
        if js_object.is_array()? {
          let mut deserializer =
            JsArrayAccess::new(&js_object, js_object.get_array_length_unchecked()?);
          visitor.visit_seq(&mut deserializer)
        } else if js_object.is_typedarray()? {
          visitor.visit_bytes(unsafe { FromNapiValue::from_napi_value(self.0.env, self.0.value)? })
        } else if js_object.is_buffer()? {
          visitor.visit_bytes(&unsafe { BufferSlice::from_napi_value(self.0.env, self.0.value)? })
        } else if js_object.is_arraybuffer()? {
          let array_buf = unsafe { ArrayBuffer::from_napi_value(self.0.env, self.0.value)? };
          if array_buf.data.is_empty() {
            return visitor.visit_bytes(&[]);
          }
          visitor.visit_bytes(array_buf.data)
        } else {
          let mut deserializer = JsObjectAccess::new(&js_object)?;
          visitor.visit_map(&mut deserializer)
        }
      }
      #[cfg(feature = "napi6")]
      ValueType::BigInt => {
        let js_bigint = unsafe { BigInt::from_napi_value(self.0.env, self.0.value)? };

        let BigInt { sign_bit, words } = &js_bigint;
        let word_sized = words.len() < 2;

        match (sign_bit, word_sized) {
          (true, true) => visitor.visit_i64(js_bigint.get_i64().0),
          (true, false) => visitor.visit_i128(js_bigint.get_i128().0),
          (false, true) => visitor.visit_u64(js_bigint.get_u64().1),
          (false, false) => visitor.visit_u128(js_bigint.get_u128().1),
        }
      }
      ValueType::External | ValueType::Function | ValueType::Symbol => Err(Error::new(
        Status::InvalidArg,
        format!("typeof {js_value_type:?} value could not be deserialized"),
      )),
      ValueType::Unknown => unreachable!(),
    }
  }

  fn deserialize_bytes<V>(self, visitor: V) -> Result<V::Value>
  where
    V: Visitor<'x>,
  {
    match type_of!(self.0.env, self.0.value)? {
      ValueType::Object => {
        let js_object = Object::from_raw(self.0.env, self.0.value);
        if js_object.is_buffer()? {
          return visitor
            .visit_bytes(&unsafe { BufferSlice::from_napi_value(self.0.env, self.0.value)? });
        } else if js_object.is_arraybuffer()? {
          let array_buf = unsafe { ArrayBuffer::from_napi_value(self.0.env, self.0.value)? };
          if array_buf.data.is_empty() {
            return visitor.visit_bytes(&[]);
          }
          return visitor.visit_bytes(array_buf.data);
        }
        visitor.visit_bytes(unsafe { FromNapiValue::from_napi_value(self.0.env, self.0.value)? })
      }
      _ => unreachable!(),
    }
  }

  fn deserialize_byte_buf<V>(self, visitor: V) -> Result<V::Value>
  where
    V: Visitor<'x>,
  {
    match type_of!(self.0.env, self.0.value)? {
      ValueType::Object => {
        let js_object = Object::from_raw(self.0.env, self.0.value);
        if js_object.is_buffer()? {
          return visitor.visit_byte_buf(
            unsafe { BufferSlice::from_napi_value(self.0.env, self.0.value)? }.to_vec(),
          );
        } else if js_object.is_typedarray()? {
          return visitor.visit_byte_buf(unsafe {
            let u8_slice: &[u8] = FromNapiValue::from_napi_value(self.0.env, self.0.value)?;
            u8_slice.to_vec()
          });
        } else if js_object.is_arraybuffer()? {
          let array_buf = unsafe { ArrayBuffer::from_napi_value(self.0.env, self.0.value)? };
          if array_buf.data.is_empty() {
            return visitor.visit_byte_buf(Vec::new());
          }
          return visitor.visit_byte_buf(array_buf.data.to_vec());
        }
        visitor.visit_byte_buf(unsafe { FromNapiValue::from_napi_value(self.0.env, self.0.value)? })
      }
      _ => unreachable!(),
    }
  }

  fn deserialize_option<V>(self, visitor: V) -> Result<V::Value>
  where
    V: Visitor<'x>,
  {
    match type_of!(self.0.env, self.0.value)? {
      ValueType::Undefined | ValueType::Null => visitor.visit_none(),
      _ => visitor.visit_some(self),
    }
  }

  fn deserialize_enum<V>(
    self,
    _name: &'static str,
    _variants: &'static [&'static str],
    visitor: V,
  ) -> Result<V::Value>
  where
    V: Visitor<'x>,
  {
    let js_value_type = type_of!(self.0.env, self.0.value)?;
    match js_value_type {
      ValueType::String => visitor.visit_enum(JsEnumAccess::new(
        unsafe { FromNapiValue::from_napi_value(self.0.env, self.0.value) }?,
        None,
      )),
      ValueType::Object => {
        let js_object = Object::from_raw(self.0.env, self.0.value);
        let properties = js_object.get_property_names()?;
        let property_len = properties.get_array_length_unchecked()?;
        if property_len != 1 {
          Err(Error::new(
            Status::InvalidArg,
            format!("object key length: {property_len}, can not deserialize to Enum"),
          ))
        } else {
          let key = properties.get_element::<String>(0)?;
          let value: Unknown = js_object.get_named_property_unchecked(&key)?;
          visitor.visit_enum(JsEnumAccess::new(key, Some(&value.0)))
        }
      }
      _ => Err(Error::new(
        Status::InvalidArg,
        format!("{js_value_type:?} type could not deserialize to Enum type"),
      )),
    }
  }

  fn deserialize_ignored_any<V>(self, visitor: V) -> Result<V::Value>
  where
    V: Visitor<'x>,
  {
    visitor.visit_unit()
  }

  forward_to_deserialize_any! {
     <V: Visitor<'x>>
      bool i8 i16 i32 i64 i128 u8 u16 u32 u64 u128 f32 f64 char str string
      unit unit_struct seq tuple tuple_struct map struct identifier
      newtype_struct
  }
}

#[doc(hidden)]
pub(crate) struct JsEnumAccess<'env> {
  variant: String,
  value: Option<&'env Value>,
}

#[doc(hidden)]
impl<'env> JsEnumAccess<'env> {
  fn new(variant: String, value: Option<&'env Value>) -> Self {
    Self { variant, value }
  }
}

#[doc(hidden)]
impl<'de, 'env> EnumAccess<'de> for JsEnumAccess<'env> {
  type Error = Error;
  type Variant = JsVariantAccess<'env>;

  fn variant_seed<V>(self, seed: V) -> Result<(V::Value, Self::Variant)>
  where
    V: DeserializeSeed<'de>,
  {
    use serde::de::IntoDeserializer;
    let variant = self.variant.into_deserializer();
    let variant_access = JsVariantAccess { value: self.value };
    seed.deserialize(variant).map(|v| (v, variant_access))
  }
}

#[doc(hidden)]
pub(crate) struct JsVariantAccess<'env> {
  value: Option<&'env Value>,
}

#[doc(hidden)]
impl<'de> VariantAccess<'de> for JsVariantAccess<'_> {
  type Error = Error;
  fn unit_variant(self) -> Result<()> {
    match self.value {
      Some(val) => {
        let mut deserializer = De(val);
        serde::de::Deserialize::deserialize(&mut deserializer)
      }
      None => Ok(()),
    }
  }

  fn newtype_variant_seed<T>(self, seed: T) -> Result<T::Value>
  where
    T: DeserializeSeed<'de>,
  {
    match self.value {
      Some(val) => {
        let mut deserializer = De(val);
        seed.deserialize(&mut deserializer)
      }
      None => Err(serde::de::Error::invalid_type(
        Unexpected::UnitVariant,
        &"newtype variant",
      )),
    }
  }

  fn tuple_variant<V>(self, _len: usize, visitor: V) -> Result<V::Value>
  where
    V: Visitor<'de>,
  {
    match self.value {
      Some(js_value) => {
        let js_object = Object::from_raw(js_value.env, js_value.value);
        if js_object.is_array()? {
          let mut deserializer =
            JsArrayAccess::new(&js_object, js_object.get_array_length_unchecked()?);
          visitor.visit_seq(&mut deserializer)
        } else {
          Err(serde::de::Error::invalid_type(
            Unexpected::Other("JsValue"),
            &"tuple variant",
          ))
        }
      }
      None => Err(serde::de::Error::invalid_type(
        Unexpected::UnitVariant,
        &"tuple variant",
      )),
    }
  }

  fn struct_variant<V>(self, _fields: &'static [&'static str], visitor: V) -> Result<V::Value>
  where
    V: Visitor<'de>,
  {
    match self.value {
      Some(js_value) => {
        if let Ok(val) = unsafe { Object::from_napi_value(js_value.env, js_value.value) } {
          let mut deserializer = JsObjectAccess::new(&val)?;
          visitor.visit_map(&mut deserializer)
        } else {
          Err(serde::de::Error::invalid_type(
            Unexpected::Other("JsValue"),
            &"struct variant",
          ))
        }
      }
      _ => Err(serde::de::Error::invalid_type(
        Unexpected::UnitVariant,
        &"struct variant",
      )),
    }
  }
}

#[doc(hidden)]
struct JsArrayAccess<'env> {
  input: &'env Object<'env>,
  idx: u32,
  len: u32,
}

#[doc(hidden)]
impl<'env> JsArrayAccess<'env> {
  fn new(input: &'env Object, len: u32) -> Self {
    Self { input, idx: 0, len }
  }
}

#[doc(hidden)]
impl<'de> SeqAccess<'de> for JsArrayAccess<'_> {
  type Error = Error;

  fn next_element_seed<T>(&mut self, seed: T) -> Result<Option<T::Value>>
  where
    T: DeserializeSeed<'de>,
  {
    if self.idx >= self.len {
      return Ok(None);
    }
    let v = self.input.get_element::<Unknown>(self.idx)?;
    self.idx += 1;

    let mut de = De(&v.0);
    seed.deserialize(&mut de).map(Some)
  }
}

#[doc(hidden)]
pub(crate) struct JsObjectAccess<'env> {
  value: &'env Object<'env>,
  properties: Object<'env>,
  idx: u32,
  property_len: u32,
}

#[doc(hidden)]
impl<'env> JsObjectAccess<'env> {
  fn new(value: &'env Object) -> Result<Self> {
    let properties = value.get_property_names()?;
    let property_len = properties.get_array_length_unchecked()?;
    Ok(Self {
      value,
      properties,
      idx: 0,
      property_len,
    })
  }
}

#[doc(hidden)]
impl<'de> MapAccess<'de> for JsObjectAccess<'_> {
  type Error = Error;

  fn next_key_seed<K>(&mut self, seed: K) -> Result<Option<K::Value>>
  where
    K: DeserializeSeed<'de>,
  {
    if self.idx >= self.property_len {
      return Ok(None);
    }

    let prop_name = self.properties.get_element::<Unknown>(self.idx)?;

    let mut de = De(&prop_name.0);
    seed.deserialize(&mut de).map(Some)
  }

  fn next_value_seed<V>(&mut self, seed: V) -> Result<V::Value>
  where
    V: DeserializeSeed<'de>,
  {
    if self.idx >= self.property_len {
      return Err(Error::new(
        Status::InvalidArg,
        format!("Index:{} out of range: {}", self.property_len, self.idx),
      ));
    }
    let prop_name = self.properties.get_element::<String>(self.idx)?;
    let value: Unknown = self.value.get_named_property_unchecked(&prop_name)?;

    self.idx += 1;
    let mut de = De(&value.0);
    let res = seed.deserialize(&mut de)?;
    Ok(res)
  }
}
